{
	UCE - cross-platform game engine written in delphi.
	Copyright (c) 2009, Shutko Vitaly

	This file is part of UCE.

	UCE is free software; you can redistribute it and/or
	modify it under the terms of the GNU Lesser General Public
	License as published by the Free Software Foundation; either
	version 2.1 of the License, or (at your option) any later version.

	UCE is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
	Lesser General Public License for more details.

	You should have received a copy of the GNU Lesser General Public
	License along with this library; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
}

unit uSpr;

interface

uses
  uTyp, uMat, uGfx, uTex;

const
  //TSprDyn.status
  SPR_PLAYING = 0;
  SPR_PAUSED = 1;
  SPR_STOPPED = 2;

  //TSprDynAnim.playEnd
  SPR_RETURN = 0;
  SPR_FREEZE = 1;
  SPR_LOOP = 2;
  SPR_DISAPPEAR = 3;

type
  TSpr = class(TList)
    tex: TTex;
    color: TClr;
    x, y, z, w, h, angle: Single;
    cx, cy: Single;
    autocx, autocy: Boolean;
    flipx, flipy: Boolean;
    visible: Boolean;

    procedure Draw; virtual; abstract;
    procedure Update; virtual;

    constructor Create;
  end;

  TSprStc = class(TSpr)
    coords: TTexRectCoords;

    procedure Draw; overload; override;
    procedure Draw2(
      x, y, w, h, angle: Single;
      color: TClr;
      flipx: Boolean = False;
      flipy: Boolean = False;
      autocx: Boolean = True;
      autocy: Boolean = True;
      cx: Single = 0;
      cy: Single = 0); overload;
  end;

  TSprDynAnim = class(TListItem)
    coords: TTexRectCoords;
    frames: TPnt;
    frame: TPnt;
    playEnd: Integer;
    fps: Single;
  end;

  TSprDyn = class(TSpr)
    curAnim: TSprDynAnim;
    status: Cardinal;
    startTime, lastTime: Cardinal;

    function Add(name: String;
      texRect: String;
      framesx, framesy: Integer;
      fps: Single = 10;
      playEnd: Integer = SPR_DISAPPEAR;
      framex: Integer = 0;
      framey: Integer = 0): TSprDynAnim;
    function Find(name: String): TSprDynAnim;
    procedure Rem(anim: TSprDynAnim);

    procedure Play(animName: String = '');
    procedure Pause;
    procedure Stop;
    procedure SizeToAnim(animName: String = '');

    procedure Draw; override;
    procedure Update; override;
  end;

  TSprMan = class(TList)
    function AddStc(name: String;
      tex: TTex;
      texRect: String = '';
      x: Single = 0;
      y: Single = 0;
      z: Single = 0;
      w: Single = 0;
      h: Single = 0): TSprStc; overload;
    function AddDyn(name: String;
      tex: TTex;
      x: Single = 0;
      y: Single = 0;
      z: Single = 0;
      w: Single = 0;
      h: Single = 0): TSprDyn; overload;
    function FindStc(name: String): TSprStc;
    function FindDyn(name: String): TSprDyn;

    procedure Draw;
    procedure Update;
  end;

implementation

uses
  uVar;

///////////////////////////////////////////////////////////////////////////////
///  TSpr
///////////////////////////////////////////////////////////////////////////////

constructor TSpr.Create;
begin
  tex := nil;
  color := MakeClr($FFFFFFFF);
  x := 0;
  y := 0;
  w := 256;
  h := 256;
  angle := 0;
  cx := 0;
  cy := 0;
  autocx := True;
  autocy := True;
  flipx := False;
  flipy := False;
  visible := True;
end;

procedure TSpr.Update; begin end;

///////////////////////////////////////////////////////////////////////////////
///  TSprStc
///////////////////////////////////////////////////////////////////////////////

var
  verts: array[0..3] of TGfxVert;

procedure TSprStc.Draw;
begin
  if visible then Draw2(x, y, w, h, angle, color, flipx, flipy, autocx, autocy, cx, cy);
end;

procedure TSprStc.Draw2(x, y, w, h, angle: Single; color: TClr; flipx: Boolean = False; flipy: Boolean = False; autocx: Boolean = True; autocy: Boolean = True; cx: Single = 0; cy: Single = 0);
var
  tempCoords: TTexRectCoords;
begin
  if autocx then cx := w / 2;
  if autocy then cy := h / 2;

  Move(coords[0], tempCoords[0], SizeOf(coords));

  if flipx then
  begin
    tempCoords[0].x := 1 - tempCoords[0].x;
    tempCoords[1].x := 1 - tempCoords[1].x;
  end;

  if flipy then
  begin
    tempCoords[0].y := 1 - tempCoords[0].y;
    tempCoords[1].y := 1 - tempCoords[1].y;
  end;

  gfx.PushTrans;
  gfx.Trans(x + cx, y + cy);
  gfx.Rotate(angle);

  verts[0] := MakeGfxVert(-cx, -cy, tempCoords[0].x, tempCoords[0].y, color);
  verts[1] := MakeGfxVert(-cx + w, -cy, tempCoords[1].x, tempCoords[0].y, color);
  verts[2] := MakeGfxVert(-cx + w, -cy + h, tempCoords[1].x, tempCoords[1].y, color);
  verts[3] := MakeGfxVert(-cx, -cy + h, tempCoords[0].x, tempCoords[1].y, color);

  if tex <> nil then tex.Bind else gfx.NoTex;

  gfx.DrawPrim(GFX_TRIFAN, 2, @verts[0]);
  gfx.PopTrans;
end;

///////////////////////////////////////////////////////////////////////////////
///  TSprDyn
///////////////////////////////////////////////////////////////////////////////

function TSprDyn.Add(name: String; texRect: String; framesx, framesy: Integer; fps: Single = 10; playEnd: Integer = SPR_DISAPPEAR; framex: Integer = 0; framey: Integer = 0): TSprDynAnim;
begin
  Result := TSprDynAnim(inherited Add(TSprDynAnim.Create));
  Result.name := name;
  Result.coords := tex.FindRect(texRect).coords;
  Result.frames := MakePnt(framesx, framesy);
  Result.frame := MakePnt(framex, framey);
  Result.playEnd := playEnd;
  Result.fps := fps;

  if size = 1 then SizeToAnim(name);
end;

function TSprDyn.Find(name: String): TSprDynAnim;
begin
  Result := TSprDynAnim(inherited Find(name));
end;

procedure TSprDyn.Rem(anim: TSprDynAnim);
begin
  if anim <> nil then
  begin
    inherited Rem(anim);
    anim.Free;
  end;
end;

procedure TSprDyn.Play(animName: String = '');
begin
  status := SPR_PLAYING;

  if animName <> '' then
  begin
    curAnim := Find(animName);
    startTime := tim.GetTime;
  end;
end;

procedure TSprDyn.Pause;
begin
  status := SPR_PAUSED;
end;

procedure TSprDyn.Stop;
begin
  if curAnim <> nil then
    curAnim.frame := MakePnt(0, 0);

  status := SPR_STOPPED;
end;

procedure TSprDyn.SizeToAnim(animName: String = '');
var
  anim: TSprDynAnim;
begin
  anim := Find(animName);

  if anim <> nil then
  begin
    w := tex.w * (anim.coords[1].x - anim.coords[0].x) / anim.frames.x;
    h := tex.h * (anim.coords[1].y - anim.coords[0].y) / anim.frames.y;
  end;
end;

procedure TSprDyn.Draw;
var
  tempCoords, temp: TTexRectCoords;
begin
  if not visible or (curAnim = nil) then Exit;
  if (status = SPR_STOPPED) and (curAnim.playEnd = SPR_DISAPPEAR) then Exit;

  if autocx then cx := w / 2;
  if autocy then cy := h / 2;

  Move(curAnim.coords[0], tempCoords[0], SizeOf(curAnim.coords));

  if flipx then
  begin
    tempCoords[0].x := 1 - tempCoords[0].x;
    tempCoords[1].x := 1 - tempCoords[1].x;
  end;

  if flipy then
  begin
    tempCoords[0].y := 1 - tempCoords[0].y;
    tempCoords[1].y := 1 - tempCoords[1].y;
  end;

  temp := tempCoords;

  tempCoords[0].x := temp[0].x + ((temp[1].x - temp[0].x) / curAnim.frames.x) * curAnim.frame.x;
  tempCoords[1].x := temp[0].x + ((temp[1].x - temp[0].x) / curAnim.frames.x) * (curAnim.frame.x + 1);
  tempCoords[0].y := temp[0].y + ((temp[1].y - temp[0].y) / curAnim.frames.y) * curAnim.frame.y;
  tempCoords[1].y := temp[0].y + ((temp[1].y - temp[0].y) / curAnim.frames.y) * (curAnim.frame.y + 1);

  gfx.PushTrans;
  gfx.Trans(x + cx, y + cy);
  gfx.Rotate(angle);

  verts[0] := MakeGfxVert(-cx, -cy, tempCoords[0].x, tempCoords[0].y, color);
  verts[1] := MakeGfxVert(-cx + w, -cy, tempCoords[1].x, tempCoords[0].y, color);
  verts[2] := MakeGfxVert(-cx + w, -cy + h, tempCoords[1].x, tempCoords[1].y, color);
  verts[3] := MakeGfxVert(-cx, -cy + h, tempCoords[0].x, tempCoords[1].y, color);

  if tex <> nil then tex.Bind else gfx.NoTex;

  gfx.DrawPrim(GFX_TRIFAN, 2, @verts[0]);
  gfx.PopTrans;
end;

procedure TSprDyn.Update;
var
  time: Cardinal;
  frame: Cardinal;
begin
  if curAnim = nil then Exit;

  time := tim.GetTime;

  if status = SPR_PLAYING then
  begin
    frame := Trunc((time - startTime) / (1000 / curAnim.fps));

    if frame > curAnim.frames.x * curAnim.frames.y  - 1 then
    begin
      if curAnim.playEnd = SPR_LOOP then Play(curAnim.name) else
      begin
        if curAnim.playEnd = SPR_RETURN then curAnim.frame := MakePnt(0, 0) else
          if curAnim.playEnd = SPR_FREEZE then curAnim.frame := MakePnt(curAnim.frames.x - 1, curAnim.frames.y - 1);

        status := SPR_STOPPED;

        Exit;
      end;
    end;

    curAnim.frame := MakePnt(
      frame - (frame div curAnim.frames.x) * curAnim.frames.x,
      frame div curAnim.frames.x
    );
  end else
  if status = SPR_PAUSED then
  begin
    startTime := startTime + (time - lastTime);
  end;

  lastTime := time;
end;

///////////////////////////////////////////////////////////////////////////////
///  TSprMan
///////////////////////////////////////////////////////////////////////////////

function TSprMan.AddStc(name: String; tex: TTex; texRect: String = ''; x: Single = 0; y: Single = 0; z: Single = 0; w: Single = 0; h: Single = 0): TSprStc;
begin
  Result := TSprStc(inherited Add(TSprStc.Create));
  Result.name := name;
  Result.tex := tex;
  Result.color := MakeClr($FFFFFFFF);
  Result.x := x;
  Result.y := y;
  Result.z := z;
  Result.w := w;
  Result.h := h;

  if tex <> nil then
  begin
    Result.coords := tex.FindRect(texRect).coords;
    
    if Result.w = 0 then Result.w := tex.w * (Result.coords[1].x - Result.coords[0].x);
    if Result.h = 0 then Result.h := tex.h * (Result.coords[1].y - Result.coords[0].y);
  end else
  begin
    Result.coords[0] := MakeVec(0, 0);
    Result.coords[1] := MakeVec(1, 1);

    if Result.w = 0 then Result.w := 64;
    if Result.h = 0 then Result.h := 64;
  end;
end;

function TSprMan.AddDyn(name: String; tex: TTex; x: Single = 0; y: Single = 0; z: Single = 0; w: Single = 0; h: Single = 0): TSprDyn;
begin
  Result := TSprDyn(inherited Add(TSprDyn.Create));
  Result.name := name;
  Result.tex := tex;
  Result.color := MakeClr($FFFFFFFF);
  Result.x := x;
  Result.y := y;
  Result.z := z;
  Result.w := w;
  Result.h := h;

  if tex <> nil then
  begin
    if Result.w = 0 then Result.w := tex.w;
    if Result.h = 0 then Result.h := tex.h;
  end;
end;

function TSprMan.FindStc(name: String): TSprStc;
var
  i: Integer;
begin
  Result := nil;

  for i := 0 to size - 1 do
    if items[i] <> nil then
      if items[i] is TSprStc then
        if items[i].name = name then
        begin
          Result := TSprStc(items[i]);
          Exit;
        end;
end;

function TSprMan.FindDyn(name: String): TSprDyn;
var
  i: Integer;
begin
  Result := nil;

  for i := 0 to size - 1 do
    if items[i] <> nil then
      if items[i] is TSprDyn then
        if items[i].name = name then
        begin
          Result := TSprDyn(items[i]);
          Exit;
        end;
end;

procedure TSprMan.Draw;
var
  i: Integer;
begin
  for i := size - 1 downto 0 do
    if items[i] <> nil then
    begin
      TSpr(items[i]).Update;
      TSpr(items[i]).Draw;
    end;
end;

procedure TSprMan.Update;
var
  i, j: Integer;
  temp: TListItem;
begin
  for j := 0 to size - 1 do
    for i := 0 to size - 2 do
      if (items[i] = nil) and (items[i + 1] <> nil) then
      begin
        items[i] := items[i + 1];
        items[i + 1] := nil;
      end else
      if (items[i] <> nil) and (items[i + 1] <> nil) then
        if TSpr(items[i]).z < TSpr(items[i + 1]).z then
        begin
          temp := items[i];
          items[i] := items[i + 1];
          items[i + 1] := temp;
        end;
end;

end.
